package pl.agh.pp.zephyr.integration;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import pl.agh.pp.zephyr.utils.LogPublisher;

/**
 * Klient oprogramowujący komunikację przez status port z gnugk
 * 
 * Tworzy wątek
 * */
public class GkClient extends LogPublisher implements Runnable {

	public GkClient() {
		super(GkController.class);
	}

	private String host;
	private int port;
	private String user;
	private String password;

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	// socket IO to communicate with the gatekeeper
	private Socket socket = new Socket();
	private InputStream istream;
	private OutputStream ostream;

	// potrzebny atomic
	private AtomicBoolean active = new AtomicBoolean(true);

	public void stop() {

		// zatrzymujemy klienta tylko raz
		// potencjalny problem z wątkami, konieczność atomika
		if (!active.getAndSet(false)) {
			return;
		}

		// wysłanie 'exit' do gatekeeper'a
		try {
			log.info("sending 'exit' command...");
			sendGkCommand("Exit");
		} catch (Exception e) {
			e.printStackTrace();
		}

		try {
			if (socket != null && !socket.isClosed()) {
				log.info("closing socket...");
				socket.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			socket = null;
		}

		log.info("Client for " + host + " stopped.");
	};

	@Override
	public synchronized void run() {
		try {
			ostream = socket.getOutputStream();
			istream = socket.getInputStream();
			BufferedReader in = new BufferedReader(new InputStreamReader(
					istream));

			login();
			String inputLine;
			StringBuilder command = prepareCommand();
			while ((inputLine = in.readLine()) != null) {
				// obcięcie końcowego średnika, który oznacza koniec komunikatu
				if (inputLine.endsWith(";")) {
					command.append(inputLine.substring(0,
							inputLine.length() - 1));
					log.config(command.toString());
					command = prepareCommand();
				} else {
					// komunikat na wiele linijek: sklejamy
					command.append('\n');
					command.append(inputLine);
				}

				if (!active.get()) {
					break;
				}
			}
		} catch (Exception e) {
			if (!socket.isClosed()) {
				e.printStackTrace();
				log.info(e.getLocalizedMessage());
			}
		} finally {
			log.info("disconnecting from gatekeeper...");
			stop();
		}
	}

	/**
	 * do the login
	 */
	private void login() {
		if ((user != null) && (user.length() > 0)) {
			sendGkCommand(user);
			sendGkCommand(password);
		}
	}

	private StringBuilder prepareCommand() {
		StringBuilder command = new StringBuilder(getHost());
		command.append(":");
		command.append(getPort());
		command.append("#COMMAND#");
		return command;
	}

	/**
	 * helper function to send bytes to GnuGk.
	 * 
	 * @param cmd
	 */
	public void sendGkCommand(String cmd) {
		if (ostream != null && !socket.isOutputShutdown()) {
			try {
				ostream.write((cmd + "\r\n").getBytes());
			} catch (Exception e) {
				log.severe("error at sending command: "
						+ e.getLocalizedMessage());
				e.printStackTrace();
			}
		}
	}

	public void init() throws Exception {
		try {
			log.info("connecting to gatekeeper at " + host + ":" + port + "...");
			socket.connect(new InetSocketAddress(host, port));
			log.info("connected on port " + socket.getLocalPort());
		} catch (Exception e) {
			log.severe("failed to connect: " + e.getMessage());
			throw e;
		}
	}

	public boolean isActive() {
		return active.get();
	}

}
